# ScopeHandler class and friends
#
# Copyright: Charilaos Skiadas
#
# (Feel free to) use at your own risk.
class Array
  def previous(item)
    theindex = self.index(item)
    if theindex
      return self[theindex-1]
    else
      return self[-1]
    end
  end
end

class String
  def escapeScope
    self.gsub('&','&amp;').gsub('<','&lt;').gsub('>','&gt;')
  end
  def unescapeScope
    self.gsub('&lt;','<').gsub('&gt;','>').gsub('&amp;','&')
  end
end

class MyParser
  include Enumerable
  def initialize(s)
    raise "data is nil." unless s
    @data = s
    @originalData = @data
  end
  
  def reset
    @data = @originalData
  end
  
  def eof
    eof?
  end
  
  def eof?
    @data.empty?
  end
  
  def pull
    return EOF if eof?
    if @data[0,1] == "<"
      key = (@data.slice!(/\A<[^>]*>/) ||"")[1..-2]
      if key[0,1] != "/"
        return {:type => :scopeBegin, :value => key}
      else
        return {:type => :scopeEnd, :value  => key[1..-1]}
      end
    else
      key = @data.slice!(/\A[^<]*(?=<)/) || ""
      return {:type => :text, :value => key}
    end
  end
  
  def each
    reset
    until eof
      yield pull
    end
  end
  
end

class ScopeNode
  include Enumerable
  attr_accessor :children
  attr_accessor :parent
  attr_accessor :scopeName
  attr_accessor :data
  attr_accessor :coords
  def initialize(parent = nil)
    @children = Array.new
    @parent = parent
    @data = ""
    @scopeName = nil
    @coords = Array.new
  end
  
  def add(newNode)
    @children << newNode
    newNode.parent = self
  end
  
  def depth
    if self.scopeName == :root
      return 0
    else
      return self.parent.depth + 1
    end
  end
  
  def simple?
    case parent.children.length
    when 0
      return true
    when 1
      return parent.children[0].simple?
    else
      return false
    end
  end
  def not_simple?
    return !self.simple?
  end
  def first_child?
    self.parent.children.index(self) == 0
  end
  def last_child?
    self.parent.children.index(self) == self.parent.children.length-1
  end
  def last
    self.children.last
  end
  def first
    self.children.first
  end
  def previous
    if !self.first_child?
#      puts "Here" + self.to_s
      return self.parent.children.previous(self)
    else
      candidate = self.parent.previous
      until !candidate.last do candidate = candidate.last end
      return candidate
    end
  end

  def next
    parent = self.parent
    index = parent.children.index(self)
    if index == parent.children.length-1
      return parent
    else
      return parent.children[index+1]
    end
  end

  def each
    yield self
    for child in @children 
      child.each{|i| yield i}
    end
  end
  
  def text?
    @scopeName == :text
  end

  def to_s
    "scopeName: #{@scopeName} line:#{@coords[0]} column:#{@coords[1]} data: #{@data} children: #{@children.length}"
  end

end

class ScopeTree
  include Enumerable
  
  attr_accessor :root
  def initialize(parser)
    @root = ScopeNode.new
    @root.scopeName = :root
    parse(parser)
  end

  def parse(parser)
    line = 1
    column = 1
    @node = @root
    @root.coords = [line,column]
    parser.each{ |i|
      case i[:type]
      when :scopeBegin
        newNode = ScopeNode.new(@node)
        newNode.scopeName = i[:value]
        newNode.coords = [line,column]
        @node.add(newNode)
        @node = newNode
      when :scopeEnd
        raise "Invalid closing scope: #{i[:value]}" unless @node.scopeName == i[:value]
        parent = @node.parent
        parent.data << @node.data
        @node = parent
      when :text
        newNode = ScopeNode.new(@node)
        newNode.scopeName = :text
        data = i[:value]
        newNode.data = data
        newNode.coords = [line,column]
        @node.data << newNode.data
        @node.add(newNode)
        newlines = data.count("\n")
        line += newlines
        if newlines == 0
          column += data.length
        else
          column = data.scan(/[^\n]*\z/).length+1
        end
      else
        raise "Unexpected type: #{i[:type]}"
      end
    }
    raise "Incomplete tree" unless @node == @root
  end
  
  def each
    @root.each{|i| yield i}
  end
  
end
#file = STDIN

class ScopeHandler
  include Enumerable
  def initialize(ioobject)
    if ioobject.respond_to?(:read)
      data = ioobject.read # Case where they pass us an IO object, like STDIN
    else
      data = ioobject # Case where they pass us the string directly, like STDIN.read
    end
    parser = MyParser.new(data)
    @scopeTree = ScopeTree.new(parser)
  end

  def nodeForTextLocation(location)
    desiredLine, desiredColumn = *location
    currentLine, currentColumn = 1, 1
    textNodes = @scopeTree.find_all{|i| i.text?}
    node = textNodes.each{|i|
      currentLine += i.data.count("\n")
      return i if currentLine > desiredLine
      next if currentLine < desiredLine
      currentColumn += (i.data.split("\n")[-1] || [] ).length
      return i if currentColumn >= desiredColumn
  #    pp i.data, currentLine, currentColumn
    }
    return node
  end
  
  def tree
    @scopeTree
  end
  
  def each
    @scopeTree.each{|i| yield i}
  end
  
  def coordinatesForNode(node)
    node.coords
  end
  
  def upScope(selection, location)
    selection = "" unless selection
    selection = selection.data if selection.respond_to?(:data)
    node = nodeForTextLocation(location)
    until (node.scopeName == :root) || ((node.data.include? selection) && (node.data != selection))
      node = node.parent
    end
    return node
  end
  
  def previousScope(selection, location)
    selection = "" unless selection
    return selection.previous if selection.respond_to?(:previous)
    node = nodeForTextLocation(location).parent
    if node.data == selection
      node = node.previous
    end
    return node
  end
end

# For testing purposes:
#
# file = File.open("testing2.xml")
# handler = ScopeHandler.new(file.read)
# file.close

# handler.map do |node|
#   if node.scopeName != :text
#     node.depth.times{ print "\t" }
#     print node.scopeName
#     print "\n"
#   end
# end